package wang.chuangen.session.dao;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpSession;

import org.apache.catalina.Session;
import wang.chuangen.session.db.RedisConn;
import wang.chuangen.session.util.Serialization;
import wang.chuangen.session.util.Tool;
import wang.chuangen.session.vo.RedisSession;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

/**
 * Redis数据库数据层
 * @author 王传根
 * @date 2016-5-12下午1:29:41
 */
public class RedisDao {
	/**
	 * 基本sessionId默认key
	 */
	protected static final String BASE_SESSION_ID="baseSessionId";
	/**
	 * sessionId 前缀
	 */
	protected static final String SESSION_ID_PREFIX="session_id_";
	/**
	 * session attribute 前缀
	 */
	protected static final String SESSION_ATTRIBUTE_PREFIX="session_attribute_";
	/**
	 * session 创建时间
	 */
	protected static final String CREATION_TIME="creationTime";
	
	/**
	 * Redis数据库链接
	 */
	protected RedisConn redisConn;
	
	public RedisDao(RedisConn redisConn){
		this.redisConn=redisConn;
	}
	
	/**
	 * 转换 byte key
	 * @param key
	 * @return
	 * @author 王传根
	 * @date 2016-5-13上午10:24:54
	 */
	public byte[] convertByteKey(String key){
		return key.getBytes(redisConn.getCharset());
	}
	
	/**
	 * 转换sessionId key
	 * @param sessionId
	 * @return
	 * @author 王传根
	 * @date 2016-5-13上午10:18:19
	 */
	public String convertSessionIdKey(String sessionId){
		return ""+SESSION_ID_PREFIX+sessionId;
	}
	
	/**
	 * 转换sessionAttributes key
	 * @param sessionId
	 * @return
	 * @author 王传根
	 * @date 2016-5-13上午10:18:19
	 */
	public byte[] convertSessionAttributesKey(String sessionId){
		String sessionKey=""+SESSION_ATTRIBUTE_PREFIX+sessionId;
		return sessionKey.getBytes(redisConn.getCharset());
	}
	
	/**
	 * 获取Jedis链接
	 * @return
	 * @author 王传根
	 * @date 2016-5-12下午2:15:07
	 */
	public Jedis getConn(){
		try {
			JedisPool jedisPool=redisConn.getConnPool();
			Jedis jedis=jedisPool.getResource();
			int database=redisConn.getDatabase();
			if(database>0){
				jedis.select(database);
			}
			return jedis;
		} catch (Exception e) {
			Tool.severe("获取Jedis链接时发生错误,message:"+e.getMessage());
			e.printStackTrace();
		}
		return null;
	}
	
	/**
	 * 初始化
	 * @author 王传根
	 * @date 2016-5-12下午2:24:04
	 */
	public void initialization(){
		Jedis jedis = getConn();
		if(jedis==null){
			Tool.severe("初始化RedisSessionManager失败,获取的Jedis链接为空");
			return;
		}
		try {
			if(!jedis.exists(BASE_SESSION_ID)){
				jedis.set(BASE_SESSION_ID,""+System.currentTimeMillis());
			}
		} catch (Exception e) {
			Tool.severe("初始化RedisSessionManager时发生错误,message:"+e.getMessage());
			e.printStackTrace();
		} finally {
			jedis.close();
		}
	}
	
	/**
	 * 生成新的sessionId
	 * @return
	 * @author 王传根
	 * @date 2016-5-12下午1:19:45
	 */
	public String generateSessionId(){
		Jedis jedis = getConn();
		if(jedis==null)return null;
		try {
			return ""+jedis.incr(BASE_SESSION_ID);
		} catch (Exception e) {
			Tool.severe("生成自定义sessionId时发生错误,message:"+e.getMessage());
			e.printStackTrace();
		} finally {
			jedis.close();
		}
		return null;
	}
	
	/**
	 * 仅在指定sessionId已经存在时生成新的sessionId
	 * @param sessionId
	 * @return
	 * @author 王传根
	 * @date 2016-5-12下午1:20:35
	 */
	public String generateSessionId(String sessionId){
		Jedis jedis = getConn();
		if(jedis==null)return sessionId;
		try {
			if(sessionId == null || sessionId.trim().length()==0 || jedis.exists(convertSessionIdKey(sessionId))){
				return ""+jedis.incr(BASE_SESSION_ID);
			}
		} catch (Exception e) {
			Tool.severe("生成自定义sessionId时发生错误,message:"+e.getMessage());
			e.printStackTrace();
		} finally {
			jedis.close();
		}
		return sessionId;
	}
	
	/**
	 * 验证sessionId是否存在
	 * @param sessionId
	 * @return
	 * @author 王传根
	 * @date 2016-5-12下午12:00:43
	 */
	public boolean isSessionExist(String sessionId){
		Jedis jedis = getConn();
		if(jedis==null)return false;
		try {
			return jedis.exists(convertSessionIdKey(sessionId));
		} catch (Exception e) {
			Tool.severe("判断sessionId是否存在时发生错误,message:"+e.getMessage());
			e.printStackTrace();
		} finally {
			jedis.close();
		}
		return false;
	}
	
	/**
	 * 获取session数量
	 * @return
	 * @author 王传根
	 * @date 2016-5-12下午1:17:51
	 */
	public int getSessionSize(){
		Jedis jedis = getConn();
		if(jedis==null)return 0;
		try {
			return jedis.keys(convertSessionIdKey("*")).size();
		} catch (Exception e) {
			Tool.severe("查询session数量时发生错误,message:"+e.getMessage());
			e.printStackTrace();
		} finally {
			jedis.close();
		}
		return 0;
	}
	
	/**
	 * 从redis加载session数据
	 * @param session
	 * @author 王传根
	 * @date 2016-5-12下午12:01:07
	 */
	public void loadSession(Session session){
		if(session==null)return;
		if(session instanceof RedisSession){
			RedisSession redisSession=(RedisSession)session;
			redisSession.onload(true);
		}
		Jedis jedis = getConn();
		if(jedis==null){
			Tool.severe("从Redis加载session数据时,获取链接失败.");
			return;
		}
		try {
			String sessionId=session.getId();
			if(sessionId == null || sessionId.trim().length() == 0){
				Tool.severe("从Redis加载session数据时,获取的sessionId无效,sessionId:"+sessionId);
				return;
			}
			Map<String,String> sessionData=jedis.hgetAll(convertSessionIdKey(sessionId));
			setSessionData(session, sessionData);
			if(session instanceof HttpSession){
				HttpSession httpSession=(HttpSession)session;
				Map<byte[],byte[]> sessionAttributes=jedis.hgetAll(convertSessionAttributesKey(sessionId));
				setSessionAttributes(httpSession, sessionAttributes);
			}
			jedis.expire(convertSessionIdKey(sessionId), redisConn.getSessionTimeout());
			jedis.expire(convertSessionAttributesKey(sessionId), redisConn.getSessionTimeout());
		} catch (Exception e) {
			Tool.severe("从Redis加载session时发生错误,message:"+e.getMessage());
			e.printStackTrace();
		} finally {
			if(session instanceof RedisSession){
				RedisSession redisSession=(RedisSession)session;
				redisSession.onload(false);
			}
			jedis.close();
		}
	}
	
	/**
	 * 设置session属性数据
	 * @param session
	 * @author 王传根
	 * @date 2016-5-12下午4:58:31
	 */
	private void setSessionData(Session session,Map<String,String> sessionData){
		if(session == null || sessionData == null || sessionData.size() == 0)return;
		try {
			String creationTimeText=sessionData.get(CREATION_TIME);
			long creationTime=Long.parseLong(creationTimeText);
			if(creationTime>0)session.setCreationTime(creationTime);
		} catch (Exception e) {
			Tool.severe("解析session数据creationTime时出错,message:"+e.getMessage());
			e.printStackTrace();
		}
	}
	
	/**
	 * 设置sessionAttributes数据
	 * @param session
	 * @param sessionAttributes
	 * @author 王传根
	 * @date 2016-5-12下午5:08:11
	 */
	private void setSessionAttributes(HttpSession session,Map<byte[],byte[]> sessionAttributes){
		if(session == null || sessionAttributes == null || sessionAttributes.size() == 0)return;
		try {
			Serialization serialization=redisConn.getSerialization();
			for (byte[] key : sessionAttributes.keySet()) {
				byte[] data=sessionAttributes.get(key);
				setSessionAttribute(session, key, data, serialization);
			}
		} catch (Exception e) {
			Tool.severe("解析sessionAttributes数据时出错,message:"+e.getMessage());
			e.printStackTrace();
		}
	}
	
	/**
	 * 设置session值
	 * @param session
	 * @param key
	 * @param data
	 * @param serialization
	 * @author 王传根
	 * @date 2016-5-13上午10:36:44
	 */
	private void setSessionAttribute(HttpSession session,byte[] key,byte[] data,Serialization serialization){
		String name=new String(key, redisConn.getCharset());
		try {
			Object value=serialization.parseObjectByte(data);
			session.setAttribute(name, value);
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
			Tool.severe("数据反序列化时出现类找不到错误,attributeName:"+name+",message:"+e.getMessage());
			session.setAttribute(name, null);
		} catch (IOException e) {
			e.printStackTrace();
			Tool.severe("数据反序列化时发生IO错误,attributeName:"+name+",message:"+e.getMessage());
			session.setAttribute(name, null);
		} catch (Exception e) {
			e.printStackTrace();
			Tool.severe("数据反序列化时出错,attributeName:"+name+",message:"+e.getMessage());
			session.setAttribute(name, null);
		}
	}
	
	/**
	 * 保存session数据到redis
	 * @param session
	 * @author 王传根
	 * @date 2016-5-12下午12:01:42
	 */
	public void saveSession(Session session){
		if(session==null)return;
		Jedis jedis = getConn();
		if(jedis==null){
			Tool.severe("存储session数据时,获取链接失败.");
			return;
		}
		try {
			String sessionId=session.getId();
			if(sessionId == null || sessionId.trim().length() == 0){
				Tool.severe("存储session数据时,获取的sessionId无效,sessionId:"+sessionId);
				return;
			}
			if(!jedis.exists(convertSessionIdKey(sessionId))){
				Map<String,String> sessionData=new HashMap<String,String>();
				sessionData.put(CREATION_TIME,""+session.getCreationTime());
				jedis.hmset(convertSessionIdKey(sessionId),sessionData);
			}
			List<byte[]> delKeys=new ArrayList<byte[]>();
			Map<byte[],byte[]> changedData=new HashMap<byte[],byte[]>();
			Serialization serialization=redisConn.getSerialization();
			if(session instanceof RedisSession){
				RedisSession redisSession=(RedisSession)session;
				for (String key : redisSession.getChangedKeys()) {
					Object data=redisSession.getAttribute(key);
					byte[] byteKey=convertByteKey(key);
					if(data == null){
						delKeys.add(byteKey);
					}else{
						setChangedData(changedData, byteKey, data, serialization);
					}
				}
			}else if(session instanceof HttpSession){
				HttpSession httpSession=(HttpSession)session;
				for (Enumeration<String> keys=httpSession.getAttributeNames();keys.hasMoreElements();) {
					String key=keys.nextElement();
					Object data=httpSession.getAttribute(key);
					byte[] byteKey=convertByteKey(key);
					if(data == null){
						delKeys.add(byteKey);
					}else{
						setChangedData(changedData, byteKey, data, serialization);
					}
				}
			}
			if(delKeys.size()>0){
				byte[][] delKeyArray=new byte[delKeys.size()][];
				for (int i = 0; i < delKeys.size(); i++) {
					delKeyArray[i]=delKeys.get(i);
				}
				jedis.hdel(convertSessionAttributesKey(sessionId), delKeyArray);
			}
			if(changedData.size()>0){
				jedis.hmset(convertSessionAttributesKey(sessionId), changedData);
			}
			jedis.expire(convertSessionIdKey(sessionId), redisConn.getSessionTimeout());
			jedis.expire(convertSessionAttributesKey(sessionId), redisConn.getSessionTimeout());
		} catch (Exception e) {
			Tool.severe("向Redis存储session数据时发生错误,message:"+e.getMessage());
			e.printStackTrace();
		} finally {
			jedis.close();
		}
	}
	
	/**
	 * 设置改变的值
	 * @param changedData
	 * @param key
	 * @param value
	 * @author 王传根
	 * @date 2016-5-13上午10:55:15
	 */
	public void setChangedData(Map<byte[],byte[]> changedData,byte[] key,Object data,Serialization serialization){
		try {
			byte[] value=serialization.toObjectByte(data);
			changedData.put(key, value);
		} catch (IOException e) {
			e.printStackTrace();
			Tool.severe("数据序列化时发生IO错误,name:"+new String(key)+",message:"+e.getMessage());
		} catch (Exception e) {
			e.printStackTrace();
			Tool.severe("数据序列化时出错,name:"+new String(key)+",message:"+e.getMessage());
		}
	}
	
	/**
	 * 删除session
	 * @param sessionId
	 * @author 王传根
	 * @date 2016-5-12下午1:01:31
	 */
	public void removeSession(String sessionId){
		Jedis jedis = getConn();
		if(jedis==null)return;
		try {
			jedis.del(convertSessionIdKey(sessionId));
			jedis.del(convertSessionAttributesKey(sessionId));
		} catch (Exception e) {
			Tool.severe("删除session时发生错误,message:"+e.getMessage());
			e.printStackTrace();
		} finally {
			jedis.close();
		}
	}
}
